#!/usr/bin/env python2

import os
import time
import errno

from daemon import Daemon
from utils import Exp, _dwarn, _derror, _exec_shell, _exec_system, _exec_shell1

class NodeDrop(Daemon):
    def __init__(self, node):
        self.node = node
        self.config = node.config
        self.storage = node.storage
        self.shmpath = os.path.join(self.config.shm, 'nodectl/castoff')
        self.lichd_pid = None
        pidfile = '/var/run/fusionstack_node_drop.lock'

        super(NodeDrop, self).__init__(pidfile, name='node drop')

    def __castoff_is_running(self):
        cmd = "cat %s/info" % (self.shmpath)
        out, err = _exec_shell1(cmd, p=False)
        if "running" in out:
            return True
        else:
            return False

    def __drop_start(self, diskid=None):
        if diskid is not None:
            ins_name = "%s/%d" % (self.node.instences[0].name, diskid)
        else:
            ins_name = self.node.instences[0].name

        cmd = "%s --castoff %s"%(self.config.admin, ins_name)
        ret = _exec_system(cmd)
        if ret != 0:
            if ret == errno.EPERM and self.__castoff_is_running():
                pass
            else:
                raise Exp(ret, "cmd:%s execute fail" % cmd)

    def __drop_check(self, filename):
        filepath = os.path.join(self.shmpath, filename)
        if not os.path.exists(filepath):
            return False

        with open(filepath) as fp:
            content = fp.read().strip().strip('\0').strip('\n')

        return  content == "1"

    def __drop_info(self):
        filepath = os.path.join(self.shmpath, "info")
        content = 'wait...'
        if os.path.exists(filepath):
            with open(filepath) as fp:
                content = fp.read().strip().strip('\0').strip('\n')
        print content.replace('\n', '    ') + '\r',

    def __drop_restart(self):
        pidfile = os.path.join(self.config.home, "data/status/status.pid")
        with open(pidfile, 'r') as f:
            pid = int(f.readlines()[0])

        if self.lichd_pid is None:
            self.lichd_pid = pid
            return 0

        if (self.lichd_pid != pid):
            self.lichd_pid = pid
            return True

    def __drop_failed(self):
        filepath = os.path.join(self.shmpath, "info")
        content = ''
        if os.path.exists(filepath):
            with open(filepath) as fp:
                content = fp.read().strip().strip('\0').strip('\n')
        return 'status:failed' in content

    def __drop_stopped(self):
        filepath = os.path.join(self.shmpath, "info")
        content = ''
        if os.path.exists(filepath):
            with open(filepath) as fp:
                content = fp.read().strip().strip('\0').strip('\n')
        return 'status:stopped' in content

    def __drop_stop(self):
        return self.__drop_check("stop")

    def __drop_done(self):
        return self.__drop_check("done")

    def __drop_set_launch(self):
        pid = os.getpid()
        path = os.path.join(self.shmpath, "launch_pid")

        if not os.path.exists(path):
            raise Exp(errno.ENOENT, "No such file %s" % path)

        _pid = 0
        with open(path, 'r') as f:
            c = f.read().strip().strip('\0').strip('\n')
            if c:
                _pid = int(c)

        if (pid == _pid):
            return

        value = "%d\n" % (pid)
        with open(path, 'w') as f:
            f.write(value)

    def __drop_config(self):
        self.config.hosts_load()
        self.config.dropnode([self.config.hostname])
        cluster = os.path.join(self.config.lich, "admin/cluster.py")
        _exec_shell("%s update etc"%(cluster))

    def __drop_data(self):
        os.system("rm -rf %s" % self.config.shm)
        data_dir = os.path.join(self.config.home, "data")
        os.system("rm -rf %s" % (data_dir))
        #os.system("rm -rf %s" % (self.config.hsm))

    def __drop_meta(self):
        instance = self.node.instences[0]
        while True:
            info = self.storage.nodeinfo(instance.name)

            if ('admin' in info['status']) or (''):
                _dwarn("%s is admin, stop it, wait for another admin start, start it after 30s, then move it" % (self.name))
                instance.stop()
                time.sleep(30)
                instance.start()
            elif ('meta' in info['status']):
                _dwarn("%s is meta, move it" % (self.name))
                break
            else:
                return

        self.storage.dropmeta(instance.name, force=True)

    def __drop_node(self):
        instance = self.node.instences[0]
        cmd = "%s --dropnode %s"%(self.config.admin, instance.name)
        try:
            _exec_shell(cmd)
            cmd = "etcdctl member list | grep %s | awk -F ':' '{print $1}'" % (instance.name)
            (uuid,err) = _exec_shell1(cmd)
            os.system("etcdctl member remove %s" % uuid)
        except Exp, e:
            if e.errno == errno.ENOENT:
                pass
            else:
                raise

    def __drop_finish(self, diskid=None):
        if diskid is not None:
            return

        retry = 0
        retry_max = 100
        while True:
            try:
                self.__drop_meta()
                self.__drop_config()
                self.node.stop()
                self.__drop_node()
                self.__drop_data()
                break
            except Exp, e:
                time.sleep(1)
                self.node.start()
                if (e.errno in [errno.ENOSPC, errno.EINVAL, errno.EPERM, errno.ENONET, errno.EAGAIN]):
                    retry = retry + 1
                    if (retry > retry_max):
                        _derror("retry fail, too more")
                        raise Exp(e.errno, "retry fail")
                    continue
                else:
                    raise

    def run(self, diskid=None):
        self.__drop_start(diskid)

        while True:
            time.sleep(1)

            if self.__drop_done():
                self.__drop_finish(diskid)
                print ""
                print 'drop ok'
                break

            self.__drop_info()

            self.__drop_set_launch()

            if self.__drop_stop():
                print ""
                _derror("stop")
                break

            if self.__drop_failed():
                print ""
                raise Exp(errno.EPERM, "castoff failed.")

            if self.__drop_stopped():
                print ""
                #_derror("stopped")
                #break
                raise Exp(errno.EPERM, "castoff stopped.")

            if self.__drop_restart():
                raise Exp(errno.EAGAIN, "lichd restart, need retry.")

    def check(self, diskid=None):
        instance = self.node.instences[0]
        info = self.storage.nodeinfo(instance.name)
        if (time.time() - int(info['uptime']) < self.config.ADMIN_BH_WAIT):
            _derror("the cluster was just beginning, please try again after ten minutes!")
            exit(errno.EBUSY)

        if (instance.running() == False):
            _derror("%s already stopped" % (self.config.home))
            exit(errno.EBUSY)

        if diskid is not None:
            diskfile = os.path.join(self.config.home, "data/disk/disk/%d.disk" % (diskid))
            if not os.path.islink(diskfile):
                raise Exp(2, "can not find %s" % (diskfile))

    def start(self, diskid):
        retry = 0
        retry_max = 20

        while True:
            try:
                self.run(diskid)
                break
            except Exp, e:
                if (e.errno in [errno.EAGAIN]):
                    _dwarn("retry, %s" % e)
                    retry = retry + 1
                    if (retry > retry_max):
                        _derror("retry fail, too more")
                        raise Exp(e.errno, "retry fail")
                    time.sleep(10)
                else:
                    raise
